/**
 * Software License, Version 1.0 Copyright 2003 The Trustees of Indiana
 * University. All rights reserved. Redistribution and use in source and binary
 * forms, with or without modification, are permitted provided that the
 * following conditions are met: 1) All redistributions of source code must
 * retain the above copyright notice, the list of authors in the original source
 * code, this list of conditions and the disclaimer listed in this license; 2)
 * All redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the disclaimer listed in this license in the
 * documentation and/or other materials provided with the distribution; 3) Any
 * documentation included with all redistributions must include the following
 * acknowledgement: "This product includes software developed by the Community
 * Grids Lab. For further information contact the Community Grids Lab at
 * http://communitygrids.iu.edu/." Alternatively, this acknowledgement may
 * appear in the software itself, and wherever such third-party acknowledgments
 * normally appear. 4) The name Indiana University or Community Grids Lab or
 * NaradaBrokering, shall not be used to endorse or promote products derived
 * from this software without prior written permission from Indiana University.
 * For written permission, please contact the Advanced Research and Technology
 * Institute ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202. 5)
 * Products derived from this software may not be called NaradaBrokering, nor
 * may Indiana University or Community Grids Lab or NaradaBrokering appear in
 * their name, without prior written permission of ARTI. Indiana University
 * provides no reassurances that the source code provided does not infringe the
 * patent or any other intellectual property rights of any other entity. Indiana
 * University disclaims any liability to any recipient for claims brought by any
 * other entity based on infringement of intellectual property rights or
 * otherwise. LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH
 * NO WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY
 * GIVES NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS.
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS",
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION
 * GENERATED USING SOFTWARE.
 */
package cgl.narada.service.security.securityprovider;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

import org.apache.log4j.Logger;
import org.bouncycastle.util.encoders.Base64;

import cgl.narada.service.ServiceException;
import cgl.narada.service.security.impl.KeyStoreManagementImpl;
import cryptix.util.mime.Base64InputStream;
import cryptix.util.mime.Base64OutputStream;

/**
 * Util Class to write Certificates and private keys from keystore to files <br>
 * Created on Apr 15, 2005 <br>
 * <b>Acknowledgement: </B> Ideas borrowed from the jCertServices package
 * 
 * @author Harshawardhan Gadgil (hgadgil@grids.ucs.indiana.edu)
 */

public class CertificateUtil {

	static Logger log = Logger.getLogger("CertificateUtil");

	/**
	 * Verifies the supplied Certificate against a root public key
	 * 
	 * @param creds -
	 *            Supplied Credentials
	 * @param rootPubKey -
	 *            Root CA public key
	 * @return TRUE if the entity is verified, FALSE otherwise
	 */
	public static boolean ValidateCertificate(Certificate creds,
			PublicKey rootPubKey) {
		try {
			creds.verify(rootPubKey);
			return true;
		}
		catch (InvalidKeyException e) {
			log.error("", e);
		}
		catch (CertificateException e) {
			log.error("", e);
		}
		catch (NoSuchAlgorithmException e) {
			log.error("", e);
		}
		catch (NoSuchProviderException e) {
			log.error("", e);
		}
		catch (SignatureException e) {
			log.error("", e);
		}
		return false;
	}

	public static void SignCertificate(CertificateManager certMan,
			String csrFilePath, String alias) {

		log.info("Signing Certificate request:[ " + csrFilePath + "] for "
				+ alias);
		try {
			X509Certificate signedCert = CertificateSigner.signCertificate(
					certMan, new FileInputStream(csrFilePath),
					certMan.KEYSTORE_PASSWORD);

			CertificateUtil.saveX509Cert(signedCert, certMan.KEYSTORE_PATH
					+ "/" + alias + ".cer");

		}
		catch (Exception e) {
			log.error("", e);
		}
	}

	public static void saveCertsToFile(CertificateManager certMan,
			String alias, String outputCert, String outputKey) {
		X509Certificate rootCert = (X509Certificate) CertificateUtil
				.getCertificate(certMan, alias);
		try {
			CertificateUtil.saveX509Cert(rootCert, outputCert);
		}
		catch (GeneralSecurityException e) {
			log.error("", e);
		}
		catch (IOException e) {
			log.error("", e);
		}

		PrivateKey priv = CertificateUtil.getPrivateKey(certMan, alias);
		try {
			CertificateUtil.savePrivateKey(priv, outputKey);
		}
		catch (GeneralSecurityException e1) {
			log.error("", e1);
		}
		catch (IOException e1) {
			log.error("", e1);
		}

	}

	/**
	 * Generates a certificate for the specified entity. The only place where a
	 * Private key is returned
	 * 
	 * @param alias -
	 *            The entity for whom the certificate is to be created
	 * @param D_NAME -
	 *            The comma seperated D_NAME of the entity
	 * @return - The private key of the generated entity. NULL if error occurs
	 */
	public static PrivateKey getPrivateKey(CertificateManager certMan,
			String alias) {
		KeyStoreManagementImpl ksmi = new KeyStoreManagementImpl();
		try {
			KeyStore personalKeyStore = ksmi.createKeyStore(certMan.KEYSTORE,
					certMan.KEYSTORE_TYPE, certMan.KEYSTORE_PROVIDER,
					certMan.KEYSTORE_PASSWORD.toCharArray());

			return (PrivateKey) ksmi.retrieveKey(personalKeyStore, alias,
					certMan.KEYSTORE_PASSWORD.toCharArray());
		}
		catch (ServiceException e1) {
			log.error("", e1);
			return null;
		}
	}

	/**
	 * Returns the public key of the entity
	 * 
	 * @param entity
	 * @return The public key if found, NULL otherwise
	 */
	public static PublicKey getPublicKey(CertificateManager certMan,
			String entity) {
		Certificate cert = getCertificate(certMan, entity);
		if (cert == null)
			return null;
		else
			return cert.getPublicKey();
	}

	/**
	 * Returns the X.509 certificate of specified entity from the keystore
	 * 
	 * @param entity
	 * @return X.509 certificate if found, NULL otherwise
	 */
	public static Certificate getCertificate(CertificateManager certMan,
			String entity) {
		KeyStore keyStore = getKeyStore(certMan, entity);
		Certificate cert;
		try {
			cert = keyStore.getCertificate(entity);
		}
		catch (KeyStoreException e) {
			log.error("", e);
			return null;
		}
		return cert;
	}

	/**
	 * Loads the keystore corresponding to the the entity
	 * 
	 * @param alias
	 * @return Keystore if found, NULL otherwise
	 */
	private static KeyStore getKeyStore(CertificateManager certMan, String alias) {

		KeyStoreManagementImpl ksmi = new KeyStoreManagementImpl();
		KeyStore personalKeyStore = null;

		try {
			personalKeyStore = ksmi.createKeyStore(certMan.KEYSTORE,
					certMan.KEYSTORE_TYPE, certMan.KEYSTORE_PROVIDER,
					certMan.KEYSTORE_PASSWORD.toCharArray());
		}
		catch (ServiceException e1) {
			log.error("", e1);
			return null;
		}

		return personalKeyStore;
	}

	public static void saveX509Cert(X509Certificate cert, String outputFile)
			throws GeneralSecurityException, IOException {

		String pem = writePEM(cert.getEncoded(),
				"-----BEGIN CERTIFICATE-----\n", "-----END CERTIFICATE-----\n");

		FileOutputStream out = new FileOutputStream(outputFile);
		out.write(pem.getBytes());
		out.close();
	}

	public static void savePrivateKey(PrivateKey priv, String outputFile)
			throws GeneralSecurityException, IOException {

		String pem = writePEM(priv.getEncoded(),
				"-----BEGIN RSA PRIVATE KEY-----\n",
				"-----END RSA PRIVATE KEY-----\n");

		FileOutputStream out = new FileOutputStream(outputFile);
		out.write(pem.getBytes());
		out.close();
	}

	/**
	 * Write certficate bytes into a PEM encoded string
	 */
	public static String writePEM(byte[] bytes, String hdr, String ftr)
			throws IOException {
		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		Base64OutputStream b64os = new Base64OutputStream(bos);
		b64os.write(bytes);
		b64os.flush();
		b64os.close();

		ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
		InputStreamReader irr = new InputStreamReader(bis);
		BufferedReader r = new BufferedReader(irr);

		StringBuffer buff = new StringBuffer();
		String line;
		buff.append(hdr);

		while ((line = r.readLine()) != null) {
			buff.append(line + "\n");
		}
		buff.append(ftr);
		return buff.toString();

	}

	/**
	 * readPEM: Read a PEM encoded base64 stream and decode it
	 * 
	 * @param is
	 *            Base64 PEM encoded stream
	 * @param hdr
	 *            Header delimeter (e.g. ----------CERTIFICATE---------)
	 * @param ftr
	 *            Footer delimeter (e.g. ----------END CERTIFICATE---------)
	 * @return decoded DER bytes
	 * @throws IOException
	 *             if a read error occurs
	 */
	public static byte[] readPEM(InputStream is, String hdr, String ftr)
			throws IOException {
		Base64InputStream b64is = new Base64InputStream(is);

		InputStreamReader irr = new InputStreamReader(is);
		BufferedReader r = new BufferedReader(irr);

		StringBuffer buff = new StringBuffer();
		String line;
		boolean read = false;

		while ((line = r.readLine()) != null) {
			if (line.equals(hdr)) {
				read = true;
				continue;
			}
			if (line.equals(ftr)) read = false;
			if (read) buff.append(line);

		}
		return Base64.decode(buff.toString().getBytes());
	}

}